﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ionic.Zip;
using System.IO;
using System.ComponentModel;
using DotNETX.FileServer;
using DotNETX.Version;

namespace DotNETX.Version
{
    public class UpgradeClient
    {
        public const string UPGRADE_FILE_FORMAT = "upgrade-{0}.zip";

        /// <summary>
        /// 
        /// </summary>
        /// <param name="vUpgradeFolder">服务器端升级文件的虚拟目录</param>
        /// <param name="versionClient"></param>
        /// <param name="filemgrClient"></param>
        public UpgradeClient(string vUpgradeFolder, VersionClient versionClient, FileManagerClient filemgrClient)
        {
            FileManager = filemgrClient;
            VersionClient = versionClient;
            VirtualFolder = null == vUpgradeFolder ? "" : vUpgradeFolder;
            TempFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Temp");
            BufferSize = 2048000;
            IsBusy = false;
        }

        protected VersionClient VersionClient { get; set; }

        protected FileManagerClient FileManager { get; set; }

        /// <summary>
        /// 服务器端升级文件的虚拟目录
        /// </summary>
        public string VirtualFolder { get; protected set; }

        /// <summary>
        /// 临时目录
        /// </summary>
        public string TempFolder { get; set; }

        /// <summary>
        /// 文件读取缓存区大小，默认值2M
        /// </summary>
        public int BufferSize { get; set; }

        public bool IsBusy { get; protected set; }

        public event Action CompressPackageStart = null;
        public event Action<bool> CompressPackageComplete = null;
        public event Action<double> CompressPackageProgress = null;

        public event Action UploadPackageStart = null;
        public event Action<bool> UploadPackageComplete = null;
        public event Action<double> UploadPackageProgress = null;

        public event Action DownloadPackageStart = null;
        public event Action<bool> DownloadPackageComplete = null;
        public event Action<double> DownloadPackageProgress = null;

        public event Action ExtractPackageStart = null;
        public event Action<bool> ExtractPackageComplete = null;
        public event Action<double> ExtractPackageProgress = null;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pkgFolder">升级程序包包含的所有文件目录（自动压缩为zip文件上传）</param>
        /// <param name="versionInfo">版本号，用于命名压缩后的zip升级文件包</param>
        /// <returns></returns>
        public bool UploadPakage(string pkgFolder, VersionInfo versionInfo)
        {
            string version = versionInfo.VersionName;
            if (IsBusy) return false;
            IsBusy = true;
            string tempZip = Path.Combine(TempFolder, string.Format(UPGRADE_FILE_FORMAT, version));
            FileInfo zipFI = new FileInfo(tempZip);
            if (!Directory.Exists(zipFI.DirectoryName))
            {
                Directory.CreateDirectory(zipFI.DirectoryName);
            }
            if (zipFI.Exists) { File.Delete(zipFI.FullName); }


            BackgroundWorker bw = new BackgroundWorker();
            bw.WorkerReportsProgress = true;
            bw.DoWork += (s, e) =>
            {
                try
                {
                    using (ZipFile zip = new ZipFile(zipFI.FullName, Encoding.Default))
                    {
                        DirectoryInfo di = new DirectoryInfo(pkgFolder);
                        long total = di.GetFiles("*", SearchOption.AllDirectories).Sum((f) => { return f.Length; });
                        long count = 0;

                        zip.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression; // Ionic.Zlib.CompressionLevel.Default 含有bug
                        
                        zip.BufferSize = 2048000;
                        zip.SaveProgress += (sps, spe) =>
                        {
                            switch (spe.EventType)
                            {
                                case ZipProgressEventType.Error_Saving:
                                    e.Result = false;
                                    break;
                                case ZipProgressEventType.Saving_Completed:
                                    
                                    break;
                                case ZipProgressEventType.Saving_AfterWriteEntry:
                                    count += spe.CurrentEntry.UncompressedSize;
                                    break;
                                case ZipProgressEventType.Saving_EntryBytesRead:
                                    double p = (double)(spe.BytesTransferred + count) / total;
                                    if (double.IsNaN(p)) return;
                                    (s as BackgroundWorker).ReportProgress((int)(p * 100), p);
                                    break;
                            }
                        };
                        zip.ZipError += (ses, see) =>
                        {
                        };
                        zip.AddDirectory(pkgFolder);
                        zip.Save();
                        e.Result = true;
                    }
                }
                catch
                {
                    e.Result = false;
                }
            };
            bw.ProgressChanged += (s, e) =>
            {
                double p = e.UserState is double ? (double)e.UserState : e.ProgressPercentage / 100.0;
                if (null != CompressPackageProgress) CompressPackageProgress(p);
            };
            bw.RunWorkerCompleted += (s, e) =>
            {
                bool r = e.Result is bool ? (bool)e.Result : false;
                if (null != CompressPackageComplete) CompressPackageComplete(r);

                Action<bool, Exception> up_comp = (success, err) =>
                {
                    try
                    {
                        if (success) success = VersionClient.SetCurrentVersion(versionInfo);
                        if (null != UploadPackageComplete) UploadPackageComplete(success);
                    }
                    finally
                    {
                        IsBusy = false;
                    }
                };

                FileManager.PutFileAsync2(UploadPackageProgress, up_comp, zipFI.FullName, Path.Combine(VirtualFolder, zipFI.Name), true, BufferSize);
                if (null != UploadPackageStart) UploadPackageStart();
            };
            bw.RunWorkerAsync();
            if (null != CompressPackageStart) CompressPackageStart();

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="releaseDir">更新程序包的释放路径</param>
        /// <param name="versionInfo">版本号,用于查找服务器端zip升级文件包</param>
        /// <returns></returns>
        public bool DownloadPakage(string releaseDir, VersionInfo versionInfo = null)
        {
            if (null == versionInfo) versionInfo = VersionClient.GetLatestVersion();
            string version = versionInfo.VersionName;

            if (IsBusy) return false;
            IsBusy = true;

            string pkgFile = string.Format(UPGRADE_FILE_FORMAT, version);
            if (!FileManager.Proxy.HasFile(Path.Combine(VirtualFolder, pkgFile))) return false;

            string tempPkgFile = Path.Combine(TempFolder, pkgFile);

            BackgroundWorker bw = new BackgroundWorker();
            bw.WorkerReportsProgress = true;
            bw.DoWork += (s, e) =>
            {
                try
                {
                    if (!File.Exists(tempPkgFile))
                    {
                        e.Result = false;
                        return;
                    }
                    using (ZipFile zip = new ZipFile(tempPkgFile, Encoding.Default))
                    {
                        long total = zip.Sum((ze) => { return ze.UncompressedSize; });
                        long count = 0;
                        zip.ExtractProgress += (eps, epe) =>
                        {
                            switch (epe.EventType)
                            {
                                case ZipProgressEventType.Extracting_AfterExtractEntry:
                                    count += epe.CurrentEntry.UncompressedSize;
                                    break;
                                case ZipProgressEventType.Extracting_EntryBytesWritten:
                                    double p = (double)(epe.BytesTransferred + count) / total;
                                    if (double.IsNaN(p)) return;
                                    (s as BackgroundWorker).ReportProgress((int)(p * 100), p);
                                    break;
                            }
                        };
                        zip.ZipError += (ses, see) =>
                        {
                        };
                        zip.ExtractAll(releaseDir, ExtractExistingFileAction.OverwriteSilently);
                    }
                    e.Result = true;
                }
                finally
                {
                    IsBusy = false;
                }
            };
            bw.ProgressChanged += (s, e) =>
            {
                double p = e.UserState is double ? (double)e.UserState : e.ProgressPercentage / 100.0;
                if (null != ExtractPackageProgress) ExtractPackageProgress(p);
            };
            bw.RunWorkerCompleted += (s, e) =>
            {
                bool r = e.Result is bool ? (bool)e.Result : false;
                if (null != ExtractPackageComplete) ExtractPackageComplete(r);
            };

            Action<bool, Exception> dp_comp = (success, err) =>
            {
                if (null != DownloadPackageComplete) DownloadPackageComplete(success);
                bw.RunWorkerAsync();
                if (null != ExtractPackageStart) ExtractPackageStart();
            };

            FileManager.GetFileAsync(DownloadPackageProgress, dp_comp, Path.Combine(VirtualFolder, pkgFile), tempPkgFile, true, BufferSize);
            if (null != DownloadPackageStart) DownloadPackageStart();

            return true;
        }
    }
}
